

package SSF.OS.TCP;
/**
 * tcpSessionMaster.java
 *
 */


import SSF.OS.*;
import SSF.OS.Socket.*;
import SSF.Net.Util.*;
import SSF.Net.NIC;
import java.util.*;
import java.io.*;
import com.renesys.raceway.DML.*;
import SSF.Util.Random.RandomStream;
import SSF.Net.Net;

/**
 * This class manages all the TCP sessions, such as creating a new TCP session
 * and closing an existing TCP session. It receives messages coming from the 
 * upper level protocol and lower level protocol, demultiplex them and pass
 * them to the corresponding TCP sessions according to different IP address
 * and port number.
 */
  
public class tcpSessionMaster extends ProtocolSession{

  /** random offset of the system clocks*/
  RandomStream rng;

  /** random initial ofset of the system clock*/
  public long init_time;

  TcpSessionRegistry TCPsessnList; 

  class TcpSessionRegistry { 

    //1/25/02 Vector sessions;
    ArrayList sessions;

    //1/25/02 public Enumeration elements() { return sessions.elements(); }

    //1/25/02 added
    tcpSession FindSession(int srcPort, int DesIpAdr, int DesPort) {
      tcpSession tcp_s;
      int ns = sessions.size();
      for(int i=0; i<ns; i++) {
        tcp_s = (tcpSession)sessions.get(i);
        if ((tcp_s.srcPort == srcPort) &&
            (tcp_s.ipHdr.DEST_IP == DesIpAdr) &&
            (tcp_s.destPort == DesPort))
          return tcp_s;
      }
      return null;
    }


    /* Timers can now be private to this registry to reduce complexity */

     private SSF.OS.Timer transTimer, ackTimer; 

     public TcpSessionRegistry(int initial_size) {
       //1/25/02 sessions = new Vector(initial_size);
       sessions = new ArrayList(initial_size);

        if((tcp_opt & DELACK) != 0)
          ackTimer = new SSF.OS.Timer(inGraph(),TCP_FAST_INTERVAL) { 
             public void callback() {
               int ns = sessions.size();
               for(int i=0; i<ns; i++)
                 ((tcpSession)sessions.get(i)).fastTimeout();
               //1/25/02 for(Enumeration list=sessions.elements();
               //1/25/02     list.hasMoreElements();)
               //1/25/02   ((tcpSession)list.nextElement()).fastTimeout();
               set(TCP_FAST_INTERVAL);
             }
        };

        transTimer = new SSF.OS.Timer(inGraph(), TCP_SLOW_INTERVAL) { 
          public void callback(){
           conCounter = 0;
           actCounter = 0;
           //1/25/02 for(Enumeration list=sessions.elements();
           //1/25/02     list.hasMoreElements();) {
           //1/25/02   tcpSession tcp_sess = (tcpSession)list.nextElement();
           for(int i=0; i<sessions.size(); i++) {
             tcpSession tcp_sess = (tcpSession)sessions.get(i);
             tcp_sess.slowTimeout();
             if(tcp_sess.opened()){
               conCounter++;
               if(!tcp_sess.snd.checkIdle())actCounter++;
             }
           }
           if(INSTR != null){
             float time = (float)inGraph().now()/(float)SSF.Net.Net.seconds(1);
             INSTR.takeSnapshot(time, MasterInstr.EVT_COUNTCONNS);
           }
           set(TCP_SLOW_INTERVAL);
         }
        };
     }

    /* The timers are restarted 'as if' they were ticking since init time. */
     private void startTimers() {
        long time_since_init = inGraph().now()-init_time;
        long tick_offset = 
          TCP_SLOW_INTERVAL*(time_since_init/TCP_SLOW_INTERVAL + 1)
            - time_since_init;
        transTimer.set(tick_offset);
            
        if((tcp_opt & DELACK) != 0){
          tick_offset = 
            TCP_FAST_INTERVAL*(time_since_init/TCP_FAST_INTERVAL + 1)
              - time_since_init;
          ackTimer.set(tick_offset);
       } 
     }

    /* For higher performance the Timers are inactive when no TCP connections
     * are present.
     */
     private void stopTimers() {
       transTimer.cancel();
       if((tcp_opt & DELACK) != 0)
        ackTimer.cancel();
     }

     public void addSession(tcpSession newsess) {  
        if (sessions.isEmpty())  startTimers();
        //1/25/02 sessions.addElement(newsess);
        sessions.add(newsess);
     }
     public void removeSession(tcpSession oldsess) {
       int ns = sessions.indexOf(oldsess);
       if (ns > -1)
         sessions.remove(ns);
       //1/25/02 sessions.removeElement(oldsess);
       if (sessions.isEmpty()) stopTimers();
     }
  }

  /* List of TCP sub-session */
  //1/25/02 Vector Listener;
  ArrayList Listener;

  /* ipSession instance of the ProtocolGraph*/
  ProtocolSession ipsess;

  /* IP address of this host */
  int ipAddr;

   /* socketMaster instance of the ProtocolGraph */
  ProtocolSession socketsess;

  /** initial send sequence number */
  public long ISS = 0;
  
  /** initial send sequence number increment */
  public long ISS_INCR = 280000;
  
  /** maximum segment size, bytes */
  public int MSS = 1024;

  /** receive window size, MSS units */
  public int RcvWndSize = 16;
    
  /** send window size, MSS units */
  public int SendWndSize = 16;

  /** send buffer size, MSS units */
  public int SendBufferSize = 16;
  
  /** maximum congestion window size */ 
  public int MaxConWnd = Integer.MAX_VALUE/65535;

  /** maximum retransmission times */
  public int MaxRexmitTimes = 12;
    
  /** TCP slow timer interval, seconds */
  public long TCP_SLOW_INTERVAL = SSF.Net.Net.seconds(0.5);
  
  /** TCP fast timer interval, seconds */
  public long TCP_FAST_INTERVAL = SSF.Net.Net.seconds(0.2);

  /** TCP maximum segment lifetime, seconds*/
  public long MSL = SSF.Net.Net.seconds(120);
    
  /** TCP maximum idle time, seconds */
  public long MaxIdleTime = SSF.Net.Net.seconds(600);

  /** flag for delayed-ack option*/  
  public static short DELACK = 1;

  /** flag for fast recovery option*/  
  public static short FAST_RECOVERY = 2;

  short SACK = 4;

  short tcp_opt = 2;

  int conCounter;
  int actCounter;

  String rttDumpFile = null;
  String rexDumpFile = null;
  String cwndDumpFile = null;
  String eventDumpFile = null;

  PrintWriter conCountWr = null;
  PrintWriter rtoCountWr = null;

  MasterInstr INSTR = null;

  boolean INFO = false;
  boolean DEBUG = false;
  boolean WARN = true;

  /************************* Constructors ************************/
  public tcpSessionMaster() {
  }

  /************************ Class methods ************************/

  private int nextSessionNumber = 99; // for backward compatibility

  /** Returns the next higher automatically assigned integer session number.
   *  beginning from 0. Used to distinguish multiple tcpSessions in
   *  Instrumentation monitoring.
   */
  public int getSessionID() {
    return nextSessionNumber++;
  }

  /** initialize the tcpSessionMaster: get references to Socket session and
   *  IP session in enclosing ProtocolGraph.
   */
  public void init() throws ProtocolException {
    rng = Net.accessRandomStream(this, "tcp");
    init_time = (long)(rng.nextDouble() * TCP_SLOW_INTERVAL);

    ipsess = inGraph().SessionForName("ip");
    ipsess.open(this,null);
    ipAddr = ((NIC)((IP)ipsess).INTERFACES.elementAt(0)).ipAddr;
    socketsess = inGraph().SessionForName("socket");
    socketsess.open(this,new Long(init_time)); // WHAT IS THIS DOING --ato
    TCPsessnList = new TcpSessionRegistry(10); 
    //1/25/02 Listener = new Vector(10,5);
    Listener = new ArrayList();
    if(DEBUG) INSTR = new MasterInstr(this, conCountWr, rtoCountWr);
  }

  /** Configure the TCP parameters. Supported DML attributes are:
   *<PRE>
   * ProtocolSession [name tcp use SSF.OS.TCP.tcpSessionMaster
   *   tcpinit[
   *     ISS               %I    # initial sequence number
   *     MSS               %I    # maximum segment size, bytes
   *     RcvWndSize        %I    # receive buffer size in units of MSS
   *     SendWndSize       %I    # maximum send window size in units of MSS
   *     SendBufferSize    %I    # send buffer size in units of MSS
   *     MaxConWnd         %I    # maximum congestion window size in units of MSS
   *     MaxRexmitTimes    %I    # maximum number of retransmissions
   *     TCP_SLOW_INTERVAL %F    # granularity of TCP slow timer, sec
   *     TCP_FAST_INTERVAL %F    # granularity of TCP fast(delay-ack), sec
   *     MSL               %F    # maximum segment lifetime, sec
   *     MaxIdleTime       %F    # maximum idle time, sec
   *     delayed_ack       %S    # delayed ack option, true/false
   *     fast_recovery     %S    # use fast recovery algorithm, true/false
   *     show_report       %S    # print summary connection report, true/false
   *   ]
   *
   *   debug    %S               # if true, dump verbose TCP diagnostics to files
   *                             # for session & host (see below), true/false
   *
   *   warn     %S               # if true, prints warnings about certain
   *                             # important TCP events and errors (default: true)
   *
   *   # dump filename prefixes - actual filenames end with "_hostID_flowID.out"
   *   # for session info, and with "_hostID.out" for host info
   *   rttdump   %S              # rtt dumpfile prefix  (session)
   *   cwnddump  %S              # cwnd dumpfile prefix (session)
   *   rexdump   %S              # rexmit timer dumpfile prefix (session)
   *   eventdump %S              # dumpfile prefix for all events (session)
   *   con_count %S              # dumpfile prefix for number of connections (host)
   *   rto_count %S              # dumpfile prefix for timeout info (host)
   * ]
   * </PRE>
   */
  public void config(Configuration cfg) throws configException{
    String dumpfile;
    super.config(cfg);

    try {

      dumpfile = (String)cfg.findSingle("debug");
      if (dumpfile!=null) {
        if(dumpfile.compareTo("true") == 0)
          DEBUG = true;
      }

      String str = (String)cfg.findSingle("warn");
      if (str != null) {
        WARN = Boolean.valueOf(str).booleanValue();
      }

      rttDumpFile = (String)cfg.findSingle("rttdump");

      cwndDumpFile = (String)cfg.findSingle("cwnddump");

      rexDumpFile = (String)cfg.findSingle("rexdump");

      eventDumpFile = (String)cfg.findSingle("eventdump");

      dumpfile = (String)cfg.findSingle("con_count");
      if (dumpfile!=null) {
        dumpfile = dumpfile + "_" + ((SSF.Net.Host)inGraph()).nhi 
                 + ".out";
        conCountWr = new PrintWriter(new FileOutputStream(dumpfile));
     }

      dumpfile = (String)cfg.findSingle("rto_count");
      if (dumpfile!=null) {
        dumpfile = dumpfile + "_" + ((SSF.Net.Host)inGraph()).nhi 
                 + ".out";
        rtoCountWr = new PrintWriter(new FileOutputStream(dumpfile));
      }
    } catch (IOException ioex) {
      ioex.printStackTrace(); 
    }

    Configuration tcpinit = null;
    Object tcpinitfind = cfg.findSingle("tcpinit");
    if(tcpinitfind == null)
      return;
    if(tcpinitfind instanceof String)
      throw new configException("tcp attribute with filename value has" +
                                " been deprecated.");
    else {
      tcpinit = (Configuration)tcpinitfind;
      String strTarget = (String) tcpinit.findSingle("ISS");
      if(strTarget != null){
        ISS = (new Integer(strTarget)).longValue();
      }

      strTarget = (String) tcpinit.findSingle("MSS");
      if(strTarget != null)
        MSS = (new Integer(strTarget)).intValue();

      strTarget = (String) tcpinit.findSingle("RcvWndSize");
      if(strTarget != null)
        RcvWndSize = (new Integer(strTarget)).intValue();

      strTarget = (String) tcpinit.findSingle("SendWndSize");
      if(strTarget != null)
        SendWndSize = (new Integer(strTarget)).intValue();

      strTarget = (String) tcpinit.findSingle("MaxConWnd");
      if(strTarget != null)
        MaxConWnd = (new Integer(strTarget)).intValue();

      strTarget = (String) tcpinit.findSingle("SendBufferSize");
      if(strTarget != null)
        SendBufferSize = (new Integer(strTarget)).intValue();

      strTarget = (String) tcpinit.findSingle("MaxRexmitTimes");
      if(strTarget != null)
        MaxRexmitTimes = (new Integer(strTarget)).intValue();

      strTarget = (String) tcpinit.findSingle("TCP_SLOW_INTERVAL");
      if(strTarget != null)
        TCP_SLOW_INTERVAL = SSF.Net.Net.seconds(
                              (new Double(strTarget)).doubleValue());

      strTarget = (String) tcpinit.findSingle("TCP_FAST_INTERVAL");
      if(strTarget != null)
        TCP_FAST_INTERVAL = SSF.Net.Net.seconds(
                              (new Double(strTarget)).doubleValue());

      strTarget = (String) tcpinit.findSingle("MSL");
      if(strTarget != null)
        MSL = SSF.Net.Net.seconds((new Double(strTarget)).doubleValue());

      strTarget = (String) tcpinit.findSingle("MaxIdleTime");
      if(strTarget != null)
        MaxIdleTime = SSF.Net.Net.seconds(
                        (new Double(strTarget)).doubleValue());

      if((strTarget = (String)tcpinit.findSingle("delayed_ack")) != null){
        if(strTarget.compareTo("true") == 0) {
          tcp_opt |= DELACK;
        } else {
          tcp_opt &= ~DELACK;
        }
      }

      if((strTarget = (String)tcpinit.findSingle("fast_recovery")) != null){
        if(strTarget.compareTo("true") == 0) {
          tcp_opt |= FAST_RECOVERY;
        } else {
          tcp_opt &= ~FAST_RECOVERY;
        }
      }

      if((strTarget = (String)tcpinit.findSingle("select_ack")) != null){
        if(strTarget.compareTo("true") == 0) {
          tcp_opt |= SACK;
        } else {
          tcp_opt &= ~SACK;
        } 
      }

      if((strTarget = (String)tcpinit.findSingle("show_report")) != null){
        if(strTarget.compareTo("true") == 0)
          INFO = true;
      }
    }
  }

  /** No-op, for conformance with ProtocolSession interface. */
  public void open(ProtocolSession higher, Object request)
    throws ProtocolException {
  }

  /** Passive open of a listening server session. */
  public void openServer(ProtocolSession higher, int SrcIpAddr, int SrcPort) {
    if (null == FindServer(SrcPort)) {
      tcpSession newsessn = new tcpSession(this, (tcpSocket)higher, ipsess,
                                           SrcIpAddr, SrcPort, 0, 0);
      newsessn.PassiveOpen();
      //1/25/02 Listener.addElement(newsessn);
      Listener.add(newsessn);
    }
  }

  /** Remove a pasive server session listening on local port SrcPort.  [BJP 2002.01.17] */
  public void removeServer(int SrcIpAddr, int SrcPort) {
    tcpSession sess;
    for (int i=0; i<Listener.size(); i++) {
      sess = (tcpSession)Listener.get(i);
      if (sess.tcpHdr.SOURCE_port == SrcPort) {
        Listener.remove(i);
        return;
      }
    }
    System.err.println("TCP: could not close server: not found");
  }

  /**
   * Find the listening (server) tcpSession bound to the given local
   * port number.
   * @return tcpSession representing this server if the server exist,
   * otherwise return null
   */
  public tcpSession FindServer(int srcPort){
    tcpSession tcp_s;
    int ns = Listener.size();
    //1/25/02 Enumeration list = Listener.elements();
    //1/25/02 while (list.hasMoreElements()) {
    //1/25/02   tcp_s = (tcpSession)list.nextElement();
    for(int i=0; i<ns; i++) {
      tcp_s = (tcpSession)Listener.get(i);
      if(tcp_s.tcpHdr.SOURCE_port == srcPort)
        return tcp_s;
    }
    return null;
  }

  /** Actively open a connection. */
  public  void openConnect(ProtocolSession higher, int SrcIpAddr, int SrcPort, 
                           int DesIpAddr,int DesPort) {
    tcpSession tcp_s = FindSession(SrcPort, DesIpAddr, DesPort);
    if (tcp_s != null) {
      System.err.println("TCP: Connection already opened!");
      return;
    }
    tcpSession newsessn = new tcpSession(this, (tcpSocket)higher, ipsess,
                                         SrcIpAddr,SrcPort, DesIpAddr,DesPort);
    //((tcpSocket)higher).setTcpSessHp(newsessn);
    ((tcpSocket)higher).bindTcpSession(newsessn);
    newsessn.mySocket =  (tcpSocket)higher;
    //newsessn.flowID = ((tcpSocket)higher).sockNo; -- done in constructor
    newsessn.ActiveOpen();
    TCPsessnList.addSession(newsessn);
  }

  /** Find if the connection has been already established.
   */
  public boolean opened(int SrcPort, int DesIpAddr,int DesPort){
    tcpSession tcp_s = FindSession(SrcPort, DesIpAddr, DesPort);
    if(tcp_s != null)
      return tcp_s.opened();
    else
      return false;
  }

  /**
   * Find a session in the TCP session list by its identification
   * via source port number, destination IP address and port number.
   */
  public tcpSession FindSession(int srcPort, int DesIpAdr, int DesPort){
    return TCPsessnList.FindSession(srcPort, DesIpAdr, DesPort);
    //1/25/02 tcpSession tcp_s;
    //1/25/02 for (Enumeration list = TCPsessnList.elements(); list.hasMoreElements();){
    //1/25/02   tcp_s = (tcpSession)list.nextElement();
    //1/25/02   if ((tcp_s.srcPort == srcPort) &&
    //1/25/02      (tcp_s.ipHdr.DEST_IP == DesIpAdr) &&
    //1/25/02      (tcp_s.destPort == DesPort))
    //1/25/02     return tcp_s;
    //1/25/02 }
    //1/25/02 return null;
  }


  /** 
   * remove the connection from the connection list
   * @param tobeRemoved tcpSession which represent this connection
   */    
  public void removeConnect(tcpSession tobeRemoved){
    TCPsessnList.removeSession(tobeRemoved);
  }

  /** 
   * Send a TCP message with given header fields, and seglen virtual
   * payload bytes. Used to send RESET signal.
   */    
  public void respond(int srcIpAddr, int desIpAddr, int srcPort, int desPort,
		      int win, long seqNo, long ackNo, byte flag, int seglen)
    throws ProtocolException{
    TCP_Header hdr = new TCP_Header(srcPort, desPort,seqNo, ackNo, win, flag);
    TCP_Message msg = new TCP_Message(hdr, srcIpAddr, desIpAddr, seglen);
    ipsess.push(msg.ipHeader(), this);
  }

  /**
   * Demoltiplex the arriving TCP/IP message to its tcpSession session in
   * the protocol graph.<p>
   *
   * If the destination tcpSession can be found, then put the message
   * to the receive buffer, otherwise, check the flag of TCP header, and
   * if it's a connection request, then find the server socket by port 
   * number and create a new tcpSession for this connection.
   */
  public boolean push(ProtocolMessage message,
                      ProtocolSession fromSession) 
    throws ProtocolException{
    if (fromSession instanceof IP) {
      TCP_Message tcp_msg = (TCP_Message)message;
      int dPort = tcp_msg.SOURCE_port;
      int sPort = tcp_msg.DEST_port;
      int desIpAddr = tcp_msg.ipHeader().SOURCE_IP; 
      int srcIpAddr = tcp_msg.ipHeader().DEST_IP;

      tcpSession tcp_s = FindSession(sPort, desIpAddr, dPort);
      if (tcp_s != null) {
        return tcp_s.push(message, fromSession);
      } 

      /* If it's a SYN segment for a new connection addressed
       * to a local port associated with a listening socket,
       * open a new tcpsession and construct a new socket for it.
       */
      else if((tcp_s == null) && (tcp_msg.flags == TCP_Header.SYN)) {

        tcp_s = FindServer(sPort);
        if (tcp_s != null) {
          tcpSocket tcpsock = tcp_s.mySocket.addSocket(sPort,desIpAddr,dPort);
          if (tcpsock != null) {
            tcpsock.bind(srcIpAddr, sPort);
            tcpSession newsess = new tcpSession(this, tcpsock, ipsess,
                                                srcIpAddr, sPort,
                                                desIpAddr, dPort);
            if(DEBUG)
              newsess.INSTR = new Instrumentation(this, newsess);

            newsess.PassiveOpen(); // sets LISTEN state
            newsess.push(message, fromSession);

            ((tcpSocket)tcpsock).bindTcpSession(newsess);
            TCPsessnList.addSession(newsess);
            return true;
          } else {
            tcpInfo("SYN from "+IP_s.IPtoString(tcp_msg.ipHeader().SOURCE_IP)
                    +" rejected, request queue overflow");
	    respond(srcIpAddr, desIpAddr, sPort, dPort, 
		    RcvWndSize*MSS, 0, tcp_msg.SEQno+1, 
		    (byte)(TCP_Header.ACK|TCP_Header.RST), 0);
          }
        } else {
           tcpInfo("SYN from "+IP_s.IPtoString(tcp_msg.ipHeader().SOURCE_IP)
                    +" rejected, no server listening on: "
                    + IP_s.IPtoString(tcp_msg.ipHeader().DEST_IP)
                    + ":" + sPort);

            // send back RST
	    respond(srcIpAddr, desIpAddr, sPort, dPort, 
		    RcvWndSize*MSS, 0, tcp_msg.SEQno+1, 
		    (byte)(TCP_Header.ACK|TCP_Header.RST), 0);
        }
      } 
    }
    return false;
  }

  void tcpInfo(String str) {
    if (WARN) {
      System.err.println(inGraph().now()/(double)SSF.Net.Net.seconds(1.0)
                         + " TCP host " + ((SSF.Net.Host)inGraph()).nhi
                         + " - " + str);
    }
  }
}
